package com.gr.experimental.model.impl.distr.operators;

public abstract class AbstractBinaryNumericOperator 
extends AbstractBinaryOperator 
implements BinaryNumericOperator {

	enum TypeConvertionCase {
		DOUBLE_DOUBLE,
		DOUBLE_LONG,
		DOUBLE_INT,
		LONG_DOUBLE,
		LONG_LONG,
		LONG_INT,
		INT_DOUBLE,
		INT_LONG,
		INT_INT;
	};
	
	protected TypeConvertionCase typeConvertionCase;
	
	/**
	 * @param argument1Type
	 * @param argument2Type
	 */
	public AbstractBinaryNumericOperator(Class<?> argument1Type,
			Class<?> argument2Type) {
		super(
				Utils.assertNumberType(argument1Type), 
				Utils.assertNumberType(argument2Type));
		typeConvertionCase = determineTypeConvertionCase(argument1Type, argument2Type);
	}

	/**
	 * @param argument1Type
	 * @param argument2Type
	 * @return
	 */
	private TypeConvertionCase determineTypeConvertionCase(
			Class<?> argument1Type, Class<?> argument2Type) {
		
		if (argument1Type.equals(Double.class)) { // case 1
			return getTypeConvertionCase(argument2Type, 
					TypeConvertionCase.DOUBLE_DOUBLE, 
					TypeConvertionCase.DOUBLE_LONG,
					TypeConvertionCase.DOUBLE_INT); 
		} else 
			if (argument1Type.equals(Long.class)) { // case 2
				return getTypeConvertionCase(argument2Type, 
						TypeConvertionCase.LONG_DOUBLE, 
						TypeConvertionCase.LONG_LONG,
						TypeConvertionCase.LONG_INT); 
		} else
			if (argument1Type.equals(Integer.class)) { // case 3
				return getTypeConvertionCase(argument2Type, 
						TypeConvertionCase.INT_DOUBLE, 
						TypeConvertionCase.INT_LONG,
						TypeConvertionCase.INT_INT); 
		} else {
			throw new AssertionError("Unexpected argument " + argument1Type);
		}
	}
	
	TypeConvertionCase getTypeConvertionCase(
			Class<?> type,
			TypeConvertionCase case1, 
			TypeConvertionCase case2,
			TypeConvertionCase case3) {
		if (argument2Type.equals(Double.class)) {
			return case1;
		}
		else if (argument2Type.equals(Long.class)) {
			return case2;
		}
		else {
			return case3;
		}

	}

	
//	private static Class<?> assertClass(Class<?> expectedClass, Class<?> argumentType) {
//		if (!expectedClass.isAssignableFrom(argumentType)) {
//			throw new AssertionError("Class " + argumentType + " expected to be assignable from: " + expectedClass);
//		}
//		return argumentType;
//	}
	
	/**
	 * {@inheritDoc}
	 * @see com.gr.experimental.model.impl.distr.operators.AbstractBinaryOperator#op(java.lang.Object, java.lang.Object)
	 */
	@Override
	public Object op(Object arg1, Object arg2) {
		Number n1 = (Number) arg1;
		Number n2 = (Number) arg2;
		
		// as can be seen - as long
		// as we use the Number object
		// 3 states would suffice instead of 9
		switch (typeConvertionCase) {
		case DOUBLE_DOUBLE: 
		case DOUBLE_LONG:   
		case DOUBLE_INT:    
		case LONG_DOUBLE:	
		case INT_DOUBLE:	
			return this.opDouble(n1.doubleValue(), n2.doubleValue());
		case LONG_LONG:	
		case LONG_INT:	
		case INT_LONG:	
			return opLong(n1.longValue(), n2.longValue()); 
		case INT_INT:	
			return opInt(n1.intValue(), n2.intValue()); 
		}
		throw new AssertionError("arg1 should be one of: Double, Long, Integer");
	}
		
	
}
